package com.jackrain.nea.shiro.stateful;


import com.alibaba.fastjson.util.TypeUtils;
import com.jackrain.nea.config.PropertiesConf;
import com.jackrain.nea.shiro.Filters;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationListener;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.SessionValidationScheduler;
import org.apache.shiro.session.mgt.ValidatingSessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import java.util.*;

/**
 * Created by Lucky dog on 2016/7/18.
 */
@Configuration
@Slf4j
public class ShiroConfig {
    /**
     * ShiroFilterFactoryBean 处理拦截资源文件问题。
     * 注意：单独一个ShiroFilterFactoryBean配置是或报错的，以为在
     * 初始化ShiroFilterFactoryBean的时候需要注入：SecurityManager
     *
     Filter Chain定义说明
     1、一个URL可以配置多个Filter，使用逗号分隔
     2、当设置多个过滤器时，全部验证通过，才视为通过
     3、部分过滤器可指定参数，如perms，roles
     *
     */



    /**
     * 注册DelegatingFilterProxy（Shiro）
     *
     * @param
     * @return
     * @author SHANHY
     * @create  2016年1月13日
     */
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
        filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
        //  该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理
        filterRegistration.addInitParameter("targetFilterLifecycle", "false");
        filterRegistration.setEnabled(true);
        filterRegistration.addUrlPatterns("/c/**");
        filterRegistration.addUrlPatterns("/p/**");
        filterRegistration.addUrlPatterns("/app/**");
        filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistration;
    }


    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    //<!-- Enable Shiro Annotations for Spring-configured beans.  Only run after -->
    //<!-- the lifecycleBeanProcessor has run: -->
//    @Bean
//    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
//        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
//        daap.setProxyTargetClass(true);
//        return daap;
//    }
    @Bean
    public RetryLimitCredentialsMatcher retryLimitCredentialsMatcher(){
        return new RetryLimitCredentialsMatcher();
    }


    @Bean
    public MonitorAuthenticator monitorAuthenticator(Collection<AuthenticationListener> authenticationListeners,UserRealm userRealm,LDAPRealm ldapRealm){
        AtLeastOneSuccessfulStrategy successfulStrategy = new AtLeastOneSuccessfulStrategy();
        MonitorAuthenticator monitorRealm = new MonitorAuthenticator();
        monitorRealm.setAuthenticationStrategy(successfulStrategy);
        Collection<Realm> realms = new ArrayList<Realm>();
        realms.add(userRealm);
        realms.add(ldapRealm);
        monitorRealm.setRealms(realms);

        HashMap<String,Object> realmMap = new HashMap<>();
        realmMap.put("userRealm",userRealm);
        realmMap.put("ldapRealm",ldapRealm);
        monitorRealm.setDefinedRealms(realmMap);

        monitorRealm.setAuthenticationListeners(authenticationListeners);

        return monitorRealm;
    }

    @Bean
    public UserRealm userRealm(RetryLimitCredentialsMatcher retryLimitCredentialsMatcher){
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(retryLimitCredentialsMatcher);
        //关闭授权缓存域
        userRealm.setAuthorizationCachingEnabled(false);
        return userRealm;
    }

    @Bean
    public LDAPRealm ldapRealm(RetryLimitCredentialsMatcher retryLimitCredentialsMatcher){
        LDAPRealm ldapRealm = new LDAPRealm();
        ldapRealm.setCredentialsMatcher(retryLimitCredentialsMatcher);
        //关闭授权缓存域
        ldapRealm.setAuthorizationCachingEnabled(false);
        return ldapRealm;
    }


    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager securityManager,KickoutSessionControlFilter kickoutSessionControlFilter){
        System.out.println("ShiroConfiguration.shirFilter()");
        final ShiroFilterFactoryBean shiroFilterFactoryBean  = new MShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);


        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        //shiroFilterFactoryBean.setLoginUrl("/p/c/login");
        // 登录成功后要跳转的链接
        //shiroFilterFactoryBean.setSuccessUrl("/c/success");
        //拦截器.
        final Map<String,Filter> filters = new LinkedHashMap<String,Filter>();
        //拦截器.
        final Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/p/c/login", Filters.ANON);
        filterChainDefinitionMap.put("/p/c/rememberMeLogin", Filters.ANON);
        filterChainDefinitionMap.put("/c/logout", Filters.USER);
        filterChainDefinitionMap.put("/app/tpl/ctl/**", Filters.AUTHC);
        filterChainDefinitionMap.put("/p/cs/**", Filters.AUTHC);
        filterChainDefinitionMap.put("/c/**", Filters.AUTHC);
//        filterChainDefinitionMap.put("/p/c/**", Filters.);
//        filterChainDefinitionMap.put("/app/**", Filters.NO_SESSION);
        //配置退出过滤器
        filters.put(Filters.AUTHC,kickoutSessionControlFilter);

        //<!-- 过滤链定义，从上向下顺序执行，一般将 /**放在最为下边 -->:这是一个坑呢，一不小心代码就不好使了;
        //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        //filterChainDefinitionMap.put("/**", Filter.AUTHC);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        shiroFilterFactoryBean.setFilters(filters);

//        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager  securityManager(@Qualifier("sessionManagerStateful") SessionManager sessionManager,
                                                      RememberMeManager rememberMeManager,
                                                      MonitorAuthenticator monitorAuthenticator) {

        final DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setSessionManager(sessionManager);
        // TODO cache manager here
        securityManager.setRememberMeManager(rememberMeManager);
        securityManager.setAuthenticator(monitorAuthenticator);

        return securityManager;
    }



    @Bean
    public KickoutSessionControlFilter  kickoutSessionControlFilter(){
        return new KickoutSessionControlFilter();
    }




    @Configuration
    protected static class ShiroSessionConfiguration {

        @Autowired(required = false)
        Collection<SessionListener> sessionListeners;

        @Autowired
        PropertiesConf pconf;

        @Bean
        public CachingShiroSessionDao sessionDAO() {
            //LOGGER.debug("Setting session DAO");
            return new CachingShiroSessionDao();
        }


        @Bean
        public ExecutorServiceSessionValidationScheduler sessionValidationScheduler(@Qualifier("validateSessionManager") ValidatingSessionManager sessionManager){
            long interval= TypeUtils.castToLong(pconf.getProperty("raincloud.session.clean-interval"));
            ExecutorServiceSessionValidationScheduler sessionValidationScheduler = new ExecutorServiceSessionValidationScheduler();
            sessionValidationScheduler.setInterval(interval);
            sessionValidationScheduler.enableSessionValidation();
            sessionValidationScheduler.setSessionManager((ValidatingSessionManager)sessionManager);
            return sessionValidationScheduler;
        }


        @Bean
        public DefaultWebSessionManager validateSessionManager() {
            //LOGGER.debug("Creating session manager");
            final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            long timeout = TypeUtils.castToLong(pconf.getProperty("raincloud.session.timeout"));
            sessionManager.setGlobalSessionTimeout(timeout);
            //是否在会话过期后调用SessionDAO的delete方法删除会话，默认true
            sessionManager.setDeleteInvalidSessions(true);
            // Redis Recommend
            sessionManager.setSessionDAO(sessionDAO());
            // Listeners for create, stop or expiration
            if (sessionListeners != null && !sessionListeners.isEmpty()) {
                sessionManager.setSessionListeners(sessionListeners);
            }
            return sessionManager;
        }

        @Bean
        public DefaultWebSessionManager sessionManagerStateful(@Qualifier("sessionCookie") SimpleCookie sessionCookie,
                                                       SessionValidationScheduler sessionValidationScheduler) {
            //LOGGER.debug("Creating session manager");
            final DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
            // Session expire time in mill unit
            long timeout = TypeUtils.castToLong(pconf.getProperty("raincloud.session.timeout"));
            sessionManager.setGlobalSessionTimeout(timeout);

            //是否在会话过期后调用SessionDAO的delete方法删除会话，默认true
            sessionManager.setDeleteInvalidSessions(true);
            //是否开启会话验证器任务，默认true
            //sessionManager.setSessionValidationSchedulerEnabled(true);
            sessionManager.setSessionValidationScheduler(sessionValidationScheduler);
            //会话验证器调度时间
            //sessionManager.setSessionValidationInterval(60 * 1000);

            // Redis Recommend
            sessionManager.setSessionDAO(sessionDAO());
            // Listeners for create, stop or expiration
            if (sessionListeners != null && !sessionListeners.isEmpty()) {
                sessionManager.setSessionListeners(sessionListeners);
            }
            // commonly set for change session cookies name (default JSESSIOINID)
            sessionManager.setSessionIdCookie(sessionCookie);
            return sessionManager;
        }
    }





    @Configuration
    protected static class ShiroCookieConfiguration {

        @Autowired
        PropertiesConf pconf;

        @Bean(name = "sessionCookie")
        public SimpleCookie sessionCookie() {
            // LOGGER.debug("Creating session cookie bean");
            final SimpleCookie sessionCookie = new SimpleCookie("sessionCookie");
            sessionCookie.setPath("/");
            sessionCookie.setHttpOnly(true);
            sessionCookie.setMaxAge(-1);
            return sessionCookie;
        }

        @Bean
        public SimpleCookie rememberCookie() {
            //LOGGER.debug("Creating remember cookie bean");
            int maxage = TypeUtils.castToInt(pconf.getProperty("raincloud.cookie.maxage"));
            final SimpleCookie rememberCookie = new SimpleCookie("rememberCookie");
            rememberCookie.setPath("/");
            rememberCookie.setHttpOnly(true);
            // second unit
            rememberCookie.setMaxAge(maxage);
            return rememberCookie;
        }

        @Bean
        public RememberMeManager rememberMeManager(@Qualifier("rememberCookie") SimpleCookie rememberCookie) {
            // LOGGER.debug("Creating remember manager");
            final CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
            rememberMeManager.setCipherKey(org.apache.shiro.codec.Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
            rememberMeManager.setCookie(rememberCookie);
            return rememberMeManager;
        }
    }

}
